package com.nexgo.payment.pack;

import android.text.TextUtils;

import com.nexgo.common.ByteUtils;
import com.nexgo.data.entity.SendData;
import com.nexgo.data.entity.mapper.EntityDataMapper;
import com.nexgo.data.json.JsonSendData;
import com.nexgo.data.json.JsonTools;
import com.nexgo.data.json.bean.JsonBody;
import com.nexgo.data.json.bean.SendBodyData;
import com.nexgo.data.json.bean.SendHead;
import com.nexgo.iso8583.exception.WriteException;
import com.nexgo.oaf.apiv3.hsm.HSMDevice;
import com.nexgo.oaf.apiv3.hsm.TusnData;
import com.nexgo.payment.api.ApiDeviceEngine;
import com.nexgo.payment.constrant.PreferencesConstants;
import com.nexgo.payment.constrant.SysConstant;
import com.nexgo.payment.constrant.TransType;
import com.nexgo.payment.databases.DataSource;
import com.nexgo.payment.databases.mapper.TransRecordMapper;
import com.nexgo.payment.entity.MessageData;
import com.nexgo.payment.entity.TransData;
import com.nexgo.payment.util.ByteUtil;
import com.nexgo.payment.util.LenType;
import com.nexgo.payment.util.MessageHelper;
import com.nexgo.payment.util.StringUtils;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.text.SimpleDateFormat;
import java.util.Arrays;
import java.util.Date;
import java.util.Formatter;

/**
 * 所有发送包组包的父类，同时保存发送包数据到数据库
 * <p>
 * Created by xiaox on 2017/7/10.
 */
public abstract class DataBuilder {
    private static Logger mLog = LoggerFactory.getLogger(DataBuilder.class.getSimpleName());

    protected TransData mTransData;
    protected DataSource mRepository;

    protected String curruntTraceNumber;
    protected Boolean isScan;
    //wzp add
    public String getCurruntTraceNumber() {
        return curruntTraceNumber;
    }

    public void setCurruntTraceNumber(String curruntTraceNumber) {
        this.curruntTraceNumber = curruntTraceNumber;
    }

    public Boolean getScan() {
        return isScan;
    }

    public void setScan(Boolean scan) {
        isScan = scan;
    }

    DataBuilder(TransData transData, DataSource repository) {
        mTransData = transData;
        mRepository = repository;
    }

    public static DataBuilder getInstance(TransData transData, DataSource repository) {
        DataBuilder db = null;
        switch (transData.getTransType()) {
            case LOGIN:         //签到
                db = new LoginBuilder(transData, repository);
                break;
            case SALE:          //消费
            case OFF_SALE:      //脱机消费
            case EC_SALE:       //电子现金消费
                db = new SaleBuilder(transData, repository);
                break;
            case SCAN_SALE:     //扫码消费
                db = new ScanSaleBuilder(transData, repository);
                break;
            case SCAN_ORDER:
                db = new ScanOrderBuilder(transData, repository);
                break;
            case TRANS_TEST:    //回响测试
                db = new TransTestBuilder(transData, repository);
                break;
            case UPLOAD_STATE:  //状态上送
                db = new UploadStateBuilder(transData, repository);
                break;
            case AID_NOTIFY:    //AID查询
                db = new AidNotifyBuilder(transData, repository);
                break;
            case AID_DOWN:      //AID下载
                db = new AidDownBuilder(transData, repository);
                break;
            case AID_END:       //AID下载结束
                db = new AidEndBuilder(transData, repository);
                break;
            case CAPK_NOTIFY:   //CAPK查询
                db = new CapkNotifyBuilder(transData, repository);
                break;
            case CAPK_DOWN:     //CAPK下载
                db = new CapkDownBuilder(transData, repository);
                break;
            case CAPK_END:      //CAPK下载结束
                db = new CapkEndBuilder(transData, repository);
                break;
            case BLACKLIST_DOWN: //黑名单下载
                db = new BlacklistDownBuilder(transData, repository);
                break;
            case BLACKLIST_END: //黑名单下载结束
                db = new BlacklistEndBuilder(transData, repository);
                break;
            case PARAM_DOWN:   //参数传递（下载）
                db = new ParamDownBuilder(transData, repository);
                break;
            case RF_PARAM_DOWN:  //非接参数下载
                db = new RfParamDownBuilder(transData, repository);
                break;
            case QPS_BIN_B_DOWN: //免密新增Bin表下载
                db = new QpsBinBDownBuilder(transData, repository);
                break;
            case QPS_BIN_B_END: //免密新增Bin表下载结束
                db = new QpsBinBEndBuilder(transData, repository);
                break;
            case QPS_BIN_C_DOWN: //免密卡bin黑名单下载
                db = new QpsBinCDownBuilder(transData, repository);
                break;
            case QPS_BIN_C_END: //免密卡bin黑名单下载结束
                db = new QpsBinCEndBuilder(transData, repository);
                break;
            case SCRIPT_NOTIFY: //脚本通知
                db = new ScriptNotifyBuilder(transData, repository);
                break;
            case REVERSAL:      //冲正
                db = new ReversalBuilder(transData, repository);
                break;
            case QUERY:         //余额查询
                db = new BalanceBuilder(transData, repository);
                break;
            case AUTH:          //预授权
                db = new PreAuthBuilder(transData, repository);
                break;
            case AUTH_VOID:     //预授权撤销
                db = new PreAuthVoidBuilder(transData, repository);
                break;
            case AUTH_CM:       //预授权完成
                db = new PreAuthComBuilder(transData, repository);
                break;
            case AUTH_CM_VOID:  //预授权完成撤销
                db = new PreAuthComVoidBuilder(transData, repository);
                break;
            case AUTH_CM_NOTIFY: //预授权完成通知
                db = new PreAuthComNotifyBuilder(transData, repository);
                break;
            case LOGOUT:         //签退
                db = new LogoutBuilder(transData, repository);
                break;
            //lr add
            case ONLINE_REFUND:
            case REFUND:         //退货
                db = new RefundBuilder(transData, repository);
                break;
            case SCAN_REFUND:    //扫码消费退货
                db = new ScanRefundBuilder(transData, repository);
                break;
            case VOID:          //消费撤销
                db = new SaleVoidBuilder(transData, repository);
                break;
            case SCAN_VOID:     //扫码消费撤销
                db = new ScanVoidBuilder(transData, repository);
                break;
            case SETTLE:        //结算
                db = new SettleBuilder(transData, repository);
                break;
            case BATCH_END:     //结算结束
                db = new BatchEndBuilder(transData, repository);
                break;
            case BATCH_UPLOAD_MAG_OFFLINE:   //批上送磁条卡脱机交易
                db = new BatchUploadMagOfflineBuilder(transData, repository);
                break;
            case BATCH_UPLOAD_ICC_OFFLINE:   //批上送IC卡脱机交易
                db = new BatchUploadIccOfflineBuilder(transData, repository);
                break;
            case BATCH_UPLOAD_ICC_ONLINE:   //批上送IC卡联机交易
                db = new BatchUploadIccOnlineBuilder(transData, repository);
                break;
            case BATCH_UPLOAD_ICC_ONLINE_ARPC:  //批上送IC卡联机ARPC错交易
                db = new BatchUploadIccOnlineAprcBuilder(transData, repository);
                break;
            case BATCH_UPLOAD_ICC_OFFLINE_DECLINED:  //批上送IC卡脱机拒绝交易
                db = new BatchUploadIccOfflineDeclinedBuilder(transData, repository);
                break;
            case BATCH_UPLOAD_ICC_NOTIFY:  //批上送IC卡通知类交易
                db = new BatchUploadIccNotifyBuilder(transData, repository);
                break;
            case BATCH_UPLOAD_MAG_NOTIFY:  //批上送磁条卡通知类交易
                db = new BatchUploadMagNotifyBuilder(transData, repository);
                break;
            case BATCH_UPLOAD_MAG_ONLINE:  //批上送磁条卡联机交易
                db = new BatchUploadMagOnlineBuilder(transData, repository);
                break;
            case UPLOAD_ES:  //上送电子签名
                db = new UploadEsBuilder(transData, repository);
                break;
            case UPLOAD_MAG_OFFLINE:  //磁条卡离线上送
                db = new UploadMagOfflineBuilder(transData, repository);
                break;
            case UPLOAD_ICC_OFFLINE:  //IC卡脱机上送
                db = new UploadIccOfflineBuilder(transData, repository);
                break;
//            wzp add 增加扫码部分
            case SCAN_QUERY:
                db = new ScanQueryBuilder(transData, repository);
                break;
            case SCAN_CANCEL:
                db = new ScanCancelBuilder(transData, repository);
                break;
            case GPSERR_NOTIFY:
                db = new GpsErrNotifyBuilder(transData, repository);
                break;
            case RESET_GPS:
                db = new ResetGpsBuilder(transData, repository);
                break;
            case WORK_MASTER_KEY_DOWN:
                db = new WorkMasterKeyDownBuilder(transData, repository);
                break;
            case RISKTRADE:
                db = new RiskTradeBuilder(transData, repository);
                break;
            default:
                break;
        }
        return db;
    }

    protected OnCalcMacListener mListener = operaData -> {
        int mKeyIdx = Integer.parseInt(StringUtils.firstNonEmpty(mRepository.getParamValue(PreferencesConstants.MASTER_KEY_INDEX), "0"));
        return MessageHelper.calcMac(operaData, mKeyIdx);
    };

    protected OnCalcSignListener mSignListener = operaData -> {
        int mKeyIdx = Integer.parseInt(StringUtils.firstNonEmpty(mRepository.getParamValue(PreferencesConstants.MASTER_KEY_INDEX), "0"));
        return MessageHelper.calcSign(operaData, mKeyIdx);
    };

    /**
     * 外部调用方法，由于存包要以发送包为准，所以存包移到这里处理
     *
     * @return
     */
    public SendData build() {
        SendData sendData = form();
        if (mTransData.getTransType().isSaveRecord() || mTransData.getTransType().isReversal()) {
            mLog.debug("存历史库数据，流水号{}", sendData.getTraceNum());
            mRepository.insertTransRecord(new TransRecordMapper().transform(mTransData, sendData, mRepository));
        }
        return sendData;
    }

    /**
     * 组发送包
     *
     * @return
     */
    abstract SendData form();

    //lr add20180325
    /**
     * 外部调用方法，由于存包要以发送包为准，所以存包移到这里处理
     *
     * @return
     */
    public MessageData buildJson() {
        MessageData sSendData = formJsonData();
        if (mTransData.getTransType().isSaveRecord() || mTransData.getTransType().isReversal()) {
            //银行卡交易记录
            if (!getScan()) {
                mLog.debug("存历史库数据，流水号{}", sSendData.getSendData().getTraceNum());
                mRepository.insertTransRecord(new TransRecordMapper().transform(mTransData, sSendData.getSendData(), mRepository));

            }else{
                //mLog.debug("存历史库数据，流水号{}", jsonBody.getTerm_pos_no());
                if(!TransType.SCAN_QUERY.equals(mTransData.getTransType())) {
                    mRepository.insertTransRecord(new TransRecordMapper().transform(mTransData, sSendData.getJsonSendData(), mRepository));
                }
            }
        }
        return sSendData;
    }

    /**
     * 组发送包
     *
     * @return
     */
    public MessageData formJsonData(){
        MessageData sSendData = new MessageData();
        JsonSendData jsonSendData = new JsonSendData();
        SendHead head = new SendHead();
        JsonBody jsonBody = new JsonBody();

        //head
        head.setTRAN_PROCESS("");
        head.setTRAN_ID("");
        head.setTranscode("CIPP040001");
        String date = new SimpleDateFormat("yyyyMMddHHmmss", SysConstant.LOCALE).format(new Date());
        head.setTerm_trans_time(date);
        head.setMcht_id(mRepository.getParamValue(PreferencesConstants.MERCHANT_ID));
        //经纬度，签名暂时为空
        head.setLngx(mRepository.getParamValue(PreferencesConstants.LONGITUDE));
        head.setLngy(mRepository.getParamValue(PreferencesConstants.LATITUDE));
        head.setSign("0000000000000000");
        jsonSendData.setREQ_HEAD(head);

        //body
        SendData cn8583Data = form();
        sSendData.setSendData(cn8583Data);

        final EntityDataMapper mapper = new EntityDataMapper();
        String cn8583Str = null;
        try {
            cn8583Str = ByteUtils.byteArray2HexString(mapper.transform(cn8583Data));
        } catch (WriteException e) {
            e.printStackTrace();
        }
        jsonBody.set_$8583data(cn8583Str);
        jsonBody.setTerm_id(mRepository.getParamValue(PreferencesConstants.TERMINAL_ID));
        jsonBody.setTerm_batch_no(mRepository.getParamValue(PreferencesConstants.BATCH_NUMBER));
        jsonBody.setTerm_pos_no(getCurruntTraceNumber());
        jsonBody.setPin(ByteUtils.byteArray2HexString(mTransData.getPin()));

        SendBodyData sendBodyData = new SendBodyData();

        String str = JsonTools.toJson(jsonBody);
        sendBodyData.setDECRYPT(str);//去掉前后大括号
        jsonSendData.setREQ_BODY(sendBodyData);

        sSendData.setJsonSendData(jsonSendData);

        setScan(false);

        return sSendData;
    }

    //wzp add
    private String formTLVNew(String tag, String value ){
        if(TextUtils.isEmpty(tag) || TextUtils.isEmpty(value)){
            return "";
        }
        mLog.error("formTLVNew tag {} value {} length {}",tag,value,value.length());
        try {
            int len = 0;
            byte[] tmpData = new byte[128];
/*            byte[] tagData = tag.getBytes();
            mLog.debug("formTLVNew tagData {}",ByteUtils.byteArray2HexString(tagData));
            System.arraycopy(tagData, 0, tmpData, len, tagData.length);
            len += tagData.length;*/

            byte[] tagData   = ByteUtil.int2Bytes(Integer.valueOf(tag), LenType.HEX,1);
            System.arraycopy(tagData, 0, tmpData, len, tagData.length);
            len += tagData.length;

            if("04".equals(tag)){
                byte[] valueData =  ByteUtils.hexString2ByteArray(value);
                tmpData[ len ++ ]  = (byte) valueData.length;
                System.arraycopy(valueData, 0, tmpData, len, valueData.length);
                len += valueData.length;
            }else{
                tmpData[ len ++ ]  = (byte) value.length();
                System.arraycopy(ByteUtil.string2ASCIIByteArray(value), 0, tmpData, len, value.length());
                len += value.length();
            }

            //tmpData[ len ++ ]  = (byte) value.length();
            //System.arraycopy(ByteUtil.string2ASCIIByteArray(value), 0, tmpData, len, value.length());
            //len += value.length();
            mLog.debug("formTLVNew tag {}",ByteUtils.byteArray2HexString(Arrays.copyOfRange(tmpData, 0, len)));
            return ByteUtils.byteArray2HexString(Arrays.copyOfRange(tmpData, 0, len));
        } catch (Exception e) {
            e.printStackTrace();
            return "";
        }
    }

    private String formTLV(String tag, String value){
        if(TextUtils.isEmpty(tag) || TextUtils.isEmpty(value)){
            return "";
        }
        try {
            int len = 0 ;
            byte[] tmpData = new byte[256];
            byte[] tagData = tag.getBytes();
            System.arraycopy(tagData, 0, tmpData, len, tagData.length);
            len += tagData.length;
            mLog.debug("formTLV tagData {}",ByteUtils.byteArray2HexString(tagData));


            byte[] valueData =  ByteUtils.hexString2ByteArray(value);
            String A2Len = new Formatter().format("%03d", valueData.length).toString();
            System.arraycopy(ByteUtil.string2ASCIIByteArray(A2Len), 0, tmpData, len, A2Len.length());
            len += A2Len.length();

            System.arraycopy(valueData, 0, tmpData, len, valueData.length);
            mLog.debug("formTLV tmpData {}",ByteUtils.byteArray2HexString(valueData));
            len += valueData.length;
            mLog.debug("formTLV tag {}",ByteUtils.byteArray2HexString(Arrays.copyOfRange(tmpData, 0, len)));
//            sb.append(value);
            return  ByteUtils.byteArray2HexString(Arrays.copyOfRange(tmpData, 0, len));
        } catch (Exception e) {
            e.printStackTrace();
            return "";
        }
    }

    protected String formA2Data(String cardNo) {
        if (TextUtils.isEmpty(cardNo)) return "";
        //随机因子
        String randFact = cardNo.substring(cardNo.length() - 6);
        mLog.debug("随机因子：{}", randFact);
        HSMDevice hsmDevice = ApiDeviceEngine.getInstance().getHSMDevice();
        TusnData tusnData = hsmDevice.getTUSN(0, randFact.getBytes());
        //POS机不支持,直接返回
        if (tusnData == null) return "";
        //终端类型
        String terminalType = tusnData.getTerminalType();
        if (TextUtils.isEmpty(terminalType)) terminalType = "04";
        mLog.debug("终端类型：{}", terminalType);
        //唯一硬件序列号
        String uniqueCode = tusnData.getTusn();  //银联序列号
        mLog.debug("唯一硬件序列号：{}", uniqueCode);
        //唯一硬件序列号密文
        String encryptedUniqueCode = tusnData.getMac();
        mLog.debug("唯一硬件序列号密文：{}", encryptedUniqueCode);

        StringBuilder sb = new StringBuilder();
        //TAG01-设备类型   TLV
        if(TextUtils.isEmpty(terminalType)){
            terminalType = "04";
        }
        sb.append(formTLVNew("01", terminalType));

        //TAG02-终端硬件序列号  TLV
        if(!TextUtils.isEmpty(uniqueCode)){
            sb.append(formTLVNew("02", uniqueCode));
        }
        //TAG03-加密随机因子
        if(!TextUtils.isEmpty(randFact)){
            sb.append(formTLVNew("03", randFact));
        }
        //TAG04-硬件序列号密文数据
        if(!TextUtils.isEmpty(encryptedUniqueCode)){
            sb.append(formTLVNew("04", encryptedUniqueCode));
        }
        //TAG05-应用程序版本号
        String appVersionNo = "20180904";
        sb.append(formTLVNew("05",appVersionNo));
        return sb.toString();
    }

    /**
     * 组59域TLV格式数据
     *
     *
     * @return 返回tlv格式数据
     */
    protected String formField59() {
        StringBuilder sb = new StringBuilder();
        String tagA2 = formA2Data(mTransData.getPan());
        mLog.debug("A2 length {} ",tagA2.length());
        if(!TextUtils.isEmpty(tagA2)) {
            sb.append(formTLV("A2", tagA2));
        }
        return sb.toString();
    }
}
